﻿#region namespaces

using System;
using System.Diagnostics;
using System.Reactive;
using System.Reactive.Linq;
using System.Xml.Linq;
using ZOIL.DataBackend.BaseX.XQueries;
using ZOIL.DataModel;

#endregion

namespace ZOIL.DataBackend.BaseX
{
    public sealed partial class Database
    {
        private void ListenToChangesIfPossible(IPersistable obj, IVisualProperties prop)
        {
            //wenn IObjectLocallyChanged implementiert: daran hören
            var lc = obj as IObjectLocallyChanged;
            if (lc != null)
            {
                ListenToChangesOf(lc);
            }
            lc = prop as IObjectLocallyChanged;
            if (lc != null)
            {
                ListenToChangesOf(lc);
            }
        }

        private void ListenToChangesOf(IObjectLocallyChanged obj)
        {
            if (!(obj is IPersistable) && !(obj is IVisualProperties))
            {
                throw new ArgumentException("Passed object is neither IDatabaseObject nor IVisualProperties!", "obj");
            }

            var objectChanges = Observable.FromEventPattern(ev => obj.ObjectLocallyChanged += ev, ev => obj.ObjectLocallyChanged -= ev)
                                          .Do(SendChangeToOtherClients)
                                          .Throttle(DbConfig.CommitToDatabaseThrottleInterval)
                                          .Do(SaveChangeInDatabase)
                ;

            var d = objectChanges.Subscribe();
            var dbObj = obj as IPersistable;
            if (dbObj != null)
            {
                _localDbObjects.AddDatabaseObjectChangedListener(dbObj, d);
            }
            else
            {
                _localDbObjects.AddVisualPropertiesChangedListener((IVisualProperties) obj, d);
            }
        }

        #region Updates to Other Clients

        private void SendChangeToOtherClients(EventPattern<EventArgs> eventPattern)
        {
            Debug.Assert(eventPattern.Sender is IObjectLocallyChanged);
            SendChangeToOtherClients(eventPattern.Sender);
        }

        public void SendChangeToOtherClients(object changedObj)
        {
            string eventType;
            long id;
            if (changedObj is IPersistable)
            {
                eventType = EventType.Update;
                id = ((IPersistable) changedObj).DatabaseId;
            }
            else if (changedObj is IVisualProperties)
            {
                eventType = EventType.Move;
                id = ((IVisualProperties) changedObj).BelongsToDatabaseId;
            }
            else
            {
                throw new ArgumentException("Passed object is neither a IDatabaseObject nor a IVisualProperties!", "changedObj");
            }

            //per Event an andere Senden
            var serializedObj = Serializer.Serialize(changedObj);
            var xml = new XElement(eventType, new XAttribute("Id", id),
                                   DbConfig.AddAttributeToXElementWithPathNamespace(serializedObj, "Path", changedObj.GetType().FullName));
            SendEvent(xml);
        }

        #endregion

        #region Updates to Database

        private void SaveChangeInDatabase(EventPattern<EventArgs> eventPattern)
        {
            Debug.Assert(eventPattern.Sender is IObjectLocallyChanged);
            SaveChangeInDatabase(eventPattern.Sender);
        }

        public void SaveChangeInDatabase(object changedObj)
        {
            string propertyName;
            if (changedObj is IPersistable)
            {
                propertyName = DbConfig.DatabaseIdInIDatabaseObject;
            }
            else if (changedObj is IVisualProperties)
            {
                propertyName = DbConfig.DatabaseIdInIVisualProperties;
            }
            else
            {
                throw new ArgumentException("Passed object is neither a IDatabaseObject nor a IVisualProperties!", "changedObj");
            }

            //per Event an andere Senden
            var serializedObj = Serializer.Serialize(changedObj);

            var ns = changedObj.GetType().Namespace;
            Debug.Assert(ns != null, "ns != null");
            var doc = ns.Substring(0, ns.IndexOf('.'));

            var query = XQueriesLoader.GetSaveChangerQuery(DbConfig.DatabaseName, doc, serializedObj, propertyName);
            _client.QuerySingle(query);
        }

        #endregion
    }
}